
#include "OGRE/RenderSystems/GL/OgreGLPlugin.h"
#include "QOgreWidget.h"

#include <QResizeEvent>
#include <string>
#include <cassert>

#ifdef __unix__
    #include <QtX11Extras/QX11Info>
#endif


QOgreWidget::QOgreWidget( IOgreEventHandler *ogreEventHandler, QWidget* parent) : 
	QGLWidget(parent), 
	mOgreEventHandler(ogreEventHandler),
	mOgreRenderWindow(NULL),
    mOgreRoot(new Ogre::Root("plugins_d.cfg"))
	 {
      
    if (!parent) {
        throw std::runtime_error("Parent widget supplied was uninitialised!"); // interface requirement
    }

    setupResources();
    chooseSceneManager();
    setupRenderSystem();

    // Set default mipmap level (NB some APIs ignore this)
    Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5);



    setAttribute(Qt::WA_PaintOnScreen, true);
    setAttribute(Qt::WA_NoSystemBackground, true);
    setFocusPolicy(Qt::StrongFocus);

    Ogre::String winHandle;
    #ifdef WIN32
        // Windows code
        winHandle += Ogre::StringConverter::toString(this->parentWidget()->winId());
    #else
        winHandle = Ogre::StringConverter::toString((unsigned long)(QX11Info::display()));
        winHandle += ":";
        winHandle += Ogre::StringConverter::toString((unsigned int)(QX11Info::appScreen()));
        winHandle += ":";
        winHandle += Ogre::StringConverter::toString((unsigned long)(winId()));
    #endif

    Ogre::NameValuePairList params;
    params["parentWindowHandle"] = winHandle;
    params["FSAA"] = Ogre::String("8");
    int w = width();
    int h = height();
    // use the OpenGL renderer in the root config
    
    mOgreRenderWindow = mOgreRoot->createRenderWindow("OgreWidget_RenderWindow", qMax(w, 640), qMax(h, 480), false, &params);
    mOgreRenderWindow->setActive(true);
    mOgreRenderWindow->setVisible(true);

    WId ogreWinId = 0x0;
    mOgreRenderWindow->getCustomAttribute("WINDOW", &ogreWinId);
    assert(ogreWinId); // guaranteed to be valid due to the way it was created
    QWidget::create(ogreWinId);
    setAttribute(Qt::WA_OpaquePaintEvent);   // qt won't bother updating the area under/behind the render window

	createFrameListener();
	
	// Initialize physics module
	mPhysicsModule = new Physics(btVector3(0.0,0.0,0.0));

	gameState = Game::GAME_START;
	fingerFactor = 0;
}

  


void QOgreWidget::createFrameListener(void)
{
    OIS::ParamList pl;
	size_t windowHnd = 0;
    std::ostringstream windowHndStr;


    mOgreRenderWindow->getCustomAttribute("WINDOW", &windowHnd);
    windowHndStr << windowHnd;
    pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));


    // Create a params panel for displaying sample details
    Ogre::StringVector items;
    items.push_back("cam.pX");
    items.push_back("cam.pY");
    items.push_back("cam.pZ");
    items.push_back("");
    items.push_back("cam.oW");
    items.push_back("cam.oX");
    items.push_back("cam.oY");
    items.push_back("cam.oZ");
    items.push_back("");
    items.push_back("Filtering");
    items.push_back("Poly Mode");


    mOgreRoot->addFrameListener(this);
}


void QOgreWidget::setupResources(void) {
    // Load resource paths from config file
    Ogre::ConfigFile cf;
    cf.load("resources_d.cfg");

    // Go through all sections & settings in the file
    Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();

    Ogre::String secName, typeName, archName;
    while (seci.hasMoreElements()) {
        secName = seci.peekNextKey();
        Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();
        Ogre::ConfigFile::SettingsMultiMap::iterator i;
        for (i = settings->begin(); i != settings->end(); ++i) {
            typeName = i->first;
            archName = i->second;
            Ogre::ResourceGroupManager::getSingleton().addResourceLocation(archName, typeName, secName);
        }
    }
}

void QOgreWidget::setupRenderSystem() {
    // look for the openGL renderer in Ogre
    Ogre::RenderSystemList::const_iterator availableRendererIt = mOgreRoot->getAvailableRenderers().begin();
    
    while (availableRendererIt != mOgreRoot->getAvailableRenderers().end()) {
        Ogre::String rName = (*availableRendererIt)->getName();
        if (rName == "OpenGL Rendering Subsystem") {
            break;
        }
        ++availableRendererIt;
    }
    
    if (availableRendererIt == mOgreRoot->getAvailableRenderers().end()) {
        throw std::runtime_error("We were unable to find the OpenGL renderer in ogre's list, cannot continue");
    }
    
    // use the OpenGL renderer in the root config
    mRenderSystem = *availableRendererIt;
    mOgreRoot->setRenderSystem(mRenderSystem);
    mOgreRoot->initialise(false);
}


void QOgreWidget::createScene() {
    mSceneManager = mOgreRoot->createSceneManager(Ogre::ST_EXTERIOR_CLOSE);

	// Create an Entity
	initObjects();

    mCamera = mSceneManager->createCamera("QOgreWidget_Cam");
    mCamera->setPosition(-100.0,300.0,200.0);
	mCamera->lookAt(airplane->getPosition().x,airplane->getPosition().y,airplane->getPosition().z);
    mCamera->setAutoAspectRatio(true);
    mCameraMan = new OgreBites::QCameraMan(mCamera);
    mCameraMan->setCamera(mCamera);
    
    
    mCamera->setNearClipDistance(0.1);
    mCamera->setFarClipDistance(50000);
 
    if (mOgreRoot->getRenderSystem()->getCapabilities()->hasCapability(Ogre::RSC_INFINITE_FAR_PLANE))
    {
        mCamera->setFarClipDistance(0);   // enable infinite far clip distance if we can
    }
	
    mOgreViewport = mOgreRenderWindow->addViewport(mCamera);

    this->resize(800, 600);
    this->setWindowTitle("QOgreWidget demo");
    
    // Set the scene's ambient light
    mSceneManager->setAmbientLight(Ogre::ColourValue(0.5f, 0.5f, 0.5f));
    
    Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
    

	Ogre::MaterialManager::getSingleton().setDefaultTextureFiltering(Ogre::TFO_ANISOTROPIC);
    Ogre::MaterialManager::getSingleton().setDefaultAnisotropy(7);
    
    // Create a Light and set its position
    
    Ogre::Vector3 lightdir(0.55, -0.3, 0.75);
    lightdir.normalise();
 
    Ogre::Light* light = mSceneManager->createLight("tstLight");
    light->setType(Ogre::Light::LT_DIRECTIONAL);
    light->setDirection(lightdir);
    light->setDiffuseColour(Ogre::ColourValue::White);
    light->setSpecularColour(Ogre::ColourValue(0.4, 0.4, 0.4));
 
    mSceneManager->setAmbientLight(Ogre::ColourValue(0.2, 0.2, 0.2));

 
    mTerrainGlobals = OGRE_NEW Ogre::TerrainGlobalOptions();
 
    mTerrainGroup = OGRE_NEW Ogre::TerrainGroup(mSceneManager, Ogre::Terrain::ALIGN_X_Z, 513, 12000.0f);
    mTerrainGroup->setFilenameConvention(Ogre::String("Phone-flying"), Ogre::String("dat"));
    mTerrainGroup->setOrigin(Ogre::Vector3::ZERO);
	
    configureTerrainDefaults(light);

	
    for (long x = 0; x <= 0; ++x)
        for (long y = 0; y <= 0; ++y)
            defineTerrain(x, y);
 
    // sync load since we want everything in place when we start
    mTerrainGroup->loadAllTerrains(true);
 
    if (mTerrainsImported)
    {
        Ogre::TerrainGroup::TerrainIterator ti = mTerrainGroup->getTerrainIterator();
        while(ti.hasMoreElements())
        {
            Ogre::Terrain* t = ti.getNext()->instance;
            initBlendMaps(t);
        }
    }
 
    mTerrainGroup->freeTemporaryResources();
 
    Ogre::ColourValue fadeColour(0.9, 0.9, 0.9);
    mSceneManager->setFog(Ogre::FOG_LINEAR, fadeColour, 0.0, 10, 1200);
    mOgreRenderWindow->getViewport(0)->setBackgroundColour(fadeColour);
 
    Ogre::Plane plane;
    plane.d = 100;
    plane.normal = Ogre::Vector3::NEGATIVE_UNIT_Y;
 
    //mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8, 500);
    mSceneManager->setSkyPlane(true, plane, "Examples/CloudySky", 500, 20, true, 0.5, 150, 150);

}

void QOgreWidget::renderOneFrame(){
	mOgreRoot->renderOneFrame();
}

void QOgreWidget::getTerrainImage(bool flipX, bool flipY, Ogre::Image& img)
{
    img.load("terrain.png", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
    if (flipX)
        img.flipAroundY();
    if (flipY)
        img.flipAroundX();
 
}
//-------------------------------------------------------------------------------------
void QOgreWidget::defineTerrain(long x, long y)
{
    Ogre::String filename = mTerrainGroup->generateFilename(x, y);
    if (Ogre::ResourceGroupManager::getSingleton().resourceExists(mTerrainGroup->getResourceGroup(), filename))
    {
        mTerrainGroup->defineTerrain(x, y);
    }
    else
    {
        Ogre::Image img;
        getTerrainImage(x % 2 != 0, y % 2 != 0, img);
        mTerrainGroup->defineTerrain(x, y, &img);
        mTerrainsImported = true;
    }
}
//-------------------------------------------------------------------------------------
void QOgreWidget::initBlendMaps(Ogre::Terrain* terrain)
{
    Ogre::TerrainLayerBlendMap* blendMap0 = terrain->getLayerBlendMap(1);
    Ogre::TerrainLayerBlendMap* blendMap1 = terrain->getLayerBlendMap(2);
    Ogre::Real minHeight0 = 70;
    Ogre::Real fadeDist0 = 40;
    Ogre::Real minHeight1 = 70;
    Ogre::Real fadeDist1 = 15;
    float* pBlend0 = blendMap0->getBlendPointer();
    float* pBlend1 = blendMap1->getBlendPointer();
    for (Ogre::uint16 y = 0; y < terrain->getLayerBlendMapSize(); ++y)
    {
        for (Ogre::uint16 x = 0; x < terrain->getLayerBlendMapSize(); ++x)
        {
            Ogre::Real tx, ty;
 
            blendMap0->convertImageToTerrainSpace(x, y, &tx, &ty);
            Ogre::Real height = terrain->getHeightAtTerrainPosition(tx, ty);
            Ogre::Real val = (height - minHeight0) / fadeDist0;
            val = Ogre::Math::Clamp(val, (Ogre::Real)0, (Ogre::Real)1);
            *pBlend0++ = val;
 
            val = (height - minHeight1) / fadeDist1;
            val = Ogre::Math::Clamp(val, (Ogre::Real)0, (Ogre::Real)1);
            *pBlend1++ = val;
        }
    }
    blendMap0->dirty();
    blendMap1->dirty();
    blendMap0->update();
    blendMap1->update();
}
//-------------------------------------------------------------------------------------
void QOgreWidget::configureTerrainDefaults(Ogre::Light* light)
{
    // Configure global
    mTerrainGlobals->setMaxPixelError(8);
    // testing composite map
    mTerrainGlobals->setCompositeMapDistance(3000);
 
    // Important to set these so that the terrain knows what to use for derived (non-realtime) data
    mTerrainGlobals->setLightMapDirection(light->getDerivedDirection());
    mTerrainGlobals->setCompositeMapAmbient(mSceneManager->getAmbientLight());
    mTerrainGlobals->setCompositeMapDiffuse(light->getDiffuseColour());
 
    // Configure default import settings for if we use imported image
    Ogre::Terrain::ImportData& defaultimp = mTerrainGroup->getDefaultImportSettings();
    defaultimp.terrainSize = 513;
    defaultimp.worldSize = 12000.0f;
    defaultimp.inputScale = 600;
    defaultimp.minBatchSize = 33;
    defaultimp.maxBatchSize = 65;
    // textures
    defaultimp.layerList.resize(3);
    defaultimp.layerList[0].worldSize = 100;
    defaultimp.layerList[0].textureNames.push_back("dirt_grayrocky_diffusespecular.dds");
    defaultimp.layerList[0].textureNames.push_back("dirt_grayrocky_normalheight.dds");
    defaultimp.layerList[1].worldSize = 30;
    defaultimp.layerList[1].textureNames.push_back("grass_green-01_diffusespecular.dds");
    defaultimp.layerList[1].textureNames.push_back("grass_green-01_normalheight.dds");
    defaultimp.layerList[2].worldSize = 200;
    defaultimp.layerList[2].textureNames.push_back("growth_weirdfungus-03_diffusespecular.dds");
    defaultimp.layerList[2].textureNames.push_back("growth_weirdfungus-03_normalheight.dds");
}

void QOgreWidget::chooseSceneManager(void)
{
    // Get the SceneManager, in this case a generic one
    mSceneManager = mOgreRoot->createSceneManager(Ogre::ST_GENERIC);

    // Initialize the OverlaySystem (changed for Ogre 1.9)
    mOverlaySystem = new Ogre::OverlaySystem();
    mSceneManager->addRenderQueueListener(mOverlaySystem);
}


void QOgreWidget::initObjects(){
	
 	airplane = new MyObject;
	// Create an Entity
	airplane->initEntity(mSceneManager->createEntity("Airplane", "biplane.mesh"));
	airplane->setMaterial("CS482/BLUE");
    // Create a SceneNode and attach the Entity to it
	airplane->initSceneNode(mSceneManager->getRootSceneNode()->createChildSceneNode("airplaneNode"));
	airplane->getSceneNode()->attachObject(airplane->getEntity());
	
	airplane->setPosition(Ogre::Vector3(0,300.0,300.0));
	airplane->setOrientation(Ogre::Quaternion(Ogre::Degree(90),Ogre::Vector3(0,0,0)));
	airplane->setMass(50);
	//airplane->start();

	
	mPhysicsModule->setupVehicle(
		btVector3(
			airplane->getHalfExtents().x, 
			airplane->getHalfExtents().y, 
			airplane->getHalfExtents().z), 
		airplane->getMass(), 
		btVector3(
			airplane->getPosition().x, 
			airplane->getPosition().y + airplane->getHalfExtents().y, 
			airplane->getPosition().z), 
		btQuaternion(
			airplane->getOrientation().x, 
			airplane->getOrientation().y, 
			airplane->getOrientation().z, 
			airplane->getOrientation().w)
		);
}
Ogre::RenderWindow *QOgreWidget::getEmbeddedOgreWindow() {
    assert(mOgreRenderWindow); // guaranteed to be valid after construction
    return mOgreRenderWindow;
}


void QOgreWidget::paintEvent(QPaintEvent *pEvent) {
    this->update();
}


void QOgreWidget::resizeEvent(QResizeEvent *rEvent) {
    if (rEvent) {
        QWidget::resizeEvent(rEvent);
    }

    if (mOgreRenderWindow) {
        // since the underlying widget has already been updated we can source the resize values from there
        mOgreRenderWindow->reposition(x(), y());
        mOgreRenderWindow->resize(width(), height());
        mOgreRenderWindow->windowMovedOrResized();
    }
}


void QOgreWidget::mousePressEvent(QMouseEvent *event) {
    mOgreEventHandler->ogreMousePressEvent(event);
}


void QOgreWidget::mouseMoveEvent(QMouseEvent *event) {
    mOgreEventHandler->ogreMouseMoveEvent(event);
}


void QOgreWidget::keyPressEvent(QKeyEvent *event){
	mOgreEventHandler->ogreKeyPressEvent(event);
}
void QOgreWidget::keyReleaseEvent(QKeyEvent *event){
	mOgreEventHandler->ogreKeyReleaseEvent(event);
}
void QOgreWidget::update() {
    QWidget::update();

    mOgreRoot->renderOneFrame();
}

bool QOgreWidget::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
    if(mOgreRenderWindow->isClosed())
        return false; 

	mCameraMan->frameRenderingQueued(evt.timeSinceLastFrame);
	
	
	mPhysicsModule->stepSimulation(evt.timeSinceLastFrame);

	//if(gameState != Game::GAME_START){
		// airplane body
		btTransform airplaneTransform;
		mPhysicsModule->mVehicleBody->getMotionState()->getWorldTransform(airplaneTransform);

		btVector3 airplanePos = airplaneTransform.getOrigin();
		btQuaternion airplaneRot = airplaneTransform.getRotation();
		airplane->getSceneNode()->setPosition(Ogre::Vector3(airplanePos.x(), airplanePos.y() - airplane->getHalfExtents().y, airplanePos.z()));
		airplane->getSceneNode()->setOrientation(airplaneRot.w(), airplaneRot.x(), airplaneRot.y(), airplaneRot.z());
	//}


    return true;
}

OgreBites::QCameraMan* QOgreWidget::getEmbeddedQCameraMan(){
	return mCameraMan;
}

void QOgreWidget::cameraManReady(bool is){
	isCameraManReady = is;
}

void QOgreWidget::addEmbeddedFrameListener(){
	createFrameListener();
}


void QOgreWidget::setCameraMan(OgreBites::QCameraMan* camMan){
	mCameraMan = camMan;
	isCameraManReady = true;
}



Physics* QOgreWidget::getPhysicsModule(){
	return mPhysicsModule;
}


OgreBites::QCameraMan* QOgreWidget::getCameraMan(){
	return mCameraMan;
}




void QOgreWidget::airplaneMove(float dx, float dy, float dz){
	
	//airplane->move(dx,dy,dz);
	//mPhysicsModule->moveVehicle(dx,dy,dz);
}



void QOgreWidget::airplaneInpulse(float x, float y, float z){
	//mPhysicsModule->moveVehicle(x,y,z);
}


void QOgreWidget::applyForceToAirplane(float fx, float fy, float fz){
	float mult = 10;
	mPhysicsModule->applyForceToVehicle(mult*fingerFactor*airplane->getMass()*fx,mult*fingerFactor*airplane->getMass()*fy,mult*fingerFactor*airplane->getMass()*fz);
	update();
}

void QOgreWidget::applyTorqueToAirplane(float fx, float fy, float fz){
	float mult = 100;
	mPhysicsModule->applyTorqueToVehicle(mult*fingerFactor*fx,mult*fingerFactor*fy,mult*fingerFactor*fz);
	update();
}

void QOgreWidget::setAirplanePose(float azi, float pitch, float roll){
	mPhysicsModule->setPoseOfVehicle(azi, pitch, roll);
}


void QOgreWidget::setFingerFactor(int num){
	fingerFactor = num;
	/*
	if(fingerFactor > 1){ 
		mPhysicsModule->moveVehicle(1,1,1);
		update();
	}*/
}